#include "PaperLuigiFramework.h"

#pragma region Constructor/Destructor
PaperLuigiFramework::PaperLuigiFramework(void)
{
	srand((unsigned)time(0));
	m_pD3DObject = 0;
	ZeroMemory(&m_D3Dpp, sizeof(m_D3Dpp));
	ZeroMemory(&m_D3Dcaps, sizeof(m_D3Dcaps));
	ZeroMemory(&m_Font, sizeof(m_Font));
	ZeroMemory(&m_Light, sizeof(m_Light));
	ZeroMemory(&m_MaterialNorm, sizeof(m_MaterialNorm));
	ZeroMemory(keyDown, sizeof(keyDown));
	m_nWidth = 0;
	m_nHeight = 0;
	m_bVSync = false;
	m_Fps = 0.0f;
	m_MillSpf = 0.0f;
	D3DXMatrixIdentity(&rotMat);
	D3DXMatrixIdentity(&scaMat);
	D3DXMatrixIdentity(&transMat);
	D3DXMatrixIdentity(&worldMat);
	D3DXMatrixIdentity(&worldInverseTransposeMat);
	m_Attenuation012 = D3DXVECTOR3(0.0f,0.0f,0.0f);
	m_CameraSpeed = 1.5f;
	luigi = new Luigi();
	enemy  = new Enemy("Generic", 3, 3, 1, 0);
}

PaperLuigiFramework::~PaperLuigiFramework(void)
{
}
#pragma endregion

#pragma region Initialization Methods

//initialize everything that the game needs
void PaperLuigiFramework::Initialize(HWND hWnd, HINSTANCE hInst, bool bWindowed)
{
	//Grab the window width and height from the HWND
	RECT r;
	GetWindowRect(hWnd, &r);
	m_nWidth = r.right - r.left;
	m_nHeight = r.bottom - r.top;

	m_hWnd = hWnd;

	m_bVSync = false;

	//Create the information for the view matrix
	m_EyePos = D3DXVECTOR3(0,5,-10);
	m_LookAt = D3DXVECTOR3(0,0,0);
	m_UpVec = D3DXVECTOR3(0,1,0);
	m_RightVec = D3DXVECTOR3(1,0,0);

	//Creates the D3D object
	m_pD3DObject = Direct3DCreate9(D3D_SDK_VERSION); //D3D_SDK_VERSION uses a macro in order to get the version

	//Create our presentation parameters for our D3D Device, its a structure
	m_D3Dpp.hDeviceWindow = hWnd;																			//Handle to the window
	m_D3Dpp.Windowed =	bWindowed;																			//Windowed or Full screen
	m_D3Dpp.BackBufferCount = 1;																			//Number of back-Buffers
	m_D3Dpp.BackBufferFormat = D3DFMT_X8R8G8B8;																//Back-buffer pixel format
	m_D3Dpp.BackBufferHeight = m_nHeight;																	//Back-Buffer Height
	m_D3Dpp.BackBufferWidth = m_nWidth;																		//Back-Buffer width
	m_D3Dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;																//Swap Effect
	m_D3Dpp.PresentationInterval = m_bVSync ? D3DPRESENT_INTERVAL_DEFAULT : D3DPRESENT_INTERVAL_IMMEDIATE;	//deals with VSync, deals with the refresh rate of the monitor. Either it uses the default or
	m_D3Dpp.FullScreen_RefreshRateInHz = bWindowed ? 0 : D3DPRESENT_RATE_DEFAULT;							//changes the refresh rate					//Shows it immediately
	m_D3Dpp.EnableAutoDepthStencil = TRUE;																	//Enable Depth and/or stencil buffer.
	m_D3Dpp.AutoDepthStencilFormat = D3DFMT_D24S8;															//Depth/Stencil Buffer in bit format
	m_D3Dpp.Flags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;													//discard the depth/stencil buffer upon present
	m_D3Dpp.MultiSampleQuality = 0;																			//MSAA quality
	m_D3Dpp.MultiSampleType = D3DMULTISAMPLE_NONE;															//MSAA type

	//Determine Supported Vertex Processing Modes (Device caps etc.)
	DetermineCaps();

	//Create D3D Device
	m_pD3DObject->CreateDevice(
		D3DADAPTER_DEFAULT,				//Default display adapter
		D3DDEVTYPE_HAL,					//Device type to use 
		hWnd,							//Handle to our window
		m_devBehaviorFlags,				//Vertex Processing Behavior Flags (PUREDEVICE, HARDWARE_VERTEXPROCESSING, SOFTWARE_VERTEXPROCESSING)
		&m_D3Dpp,						//Presentation Parameters
		&m_pD3DDevice);					//Return a created D3D Device

	D3DXCreateFont(m_pD3DDevice,		//Device
		18,								//Height
		12,								//Width
		FW_BOLD,						//Weight
		0,								//Mip Levels
		true,							//Italics
		DEFAULT_CHARSET,				//Charset
		OUT_DEFAULT_PRECIS,				//Output Precision
		DEFAULT_QUALITY,				//Quality
		DEFAULT_PITCH | FF_DONTCARE,	//Pitch and Family
		L"Times New Roman",				//FaceName
		&m_Font);						//Font Object

	//Set the renderState
	m_pD3DDevice->SetRenderState(D3DRS_ZENABLE,		//RenderStateType
		D3DZB_TRUE);		//Value

	input.initDInput(hInst, hWnd);

	createTextures();
	createMaterials();
	createViewMat();
	createProjMat();

	menu.createMenu(m_pD3DDevice, &input);
	state = TITLE;

	D3DXCreateSprite(m_pD3DDevice, &m_Sprite);

	D3DXCreateTextureFromFileEx(m_pD3DDevice, L"placeHolderLuigi.png", 0, 0, 0, 0,
		D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_DEFAULT, 
		D3DX_DEFAULT, D3DCOLOR_XRGB(255, 255, 255), 
		&luigi->m_IIChar, 0, &luigi->m_TexChar);

	luigi->setPosition(luigi->m_IIChar.Height*.25);

	/////////////////////////////
	// Meshes
	/////////////////////////////
	D3DXCreateBox(m_pD3DDevice, 2.0f, 2.0f, 2.0f, &m_BoxMesh, 0);

}

//This method grabs and checks for certain device capabilities
void PaperLuigiFramework::DetermineCaps()
{
	m_pD3DObject->GetDeviceCaps(D3DADAPTER_DEFAULT,				//The primary adapter to check
		D3DDEVTYPE_HAL,					//Device type to check
		&m_D3Dcaps						//pointer to caps object
		);

	//checks to see if the device supports Hardware vertex processing
	if(m_D3Dcaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
	{
		m_devBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;

		//checks to see if the device supports a pure device type.
		if(m_D3Dcaps.DevCaps & D3DDEVCAPS_PUREDEVICE)
		{
			m_devBehaviorFlags |= D3DCREATE_PUREDEVICE;
		}
	}
	else 
	{
		m_devBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
	}

	//to be added: More capabilites to be checked. Added as things are required

}

void PaperLuigiFramework::createTextures()
{
}

void PaperLuigiFramework::createMaterials()
{
	//create the light structure
	m_Light.Ambient = D3DXCOLOR(1.0f,1.0f,1.0f,1.0f);
	m_Light.Diffuse = D3DXCOLOR(1.0f,1.0f,1.0f,1.0f);
	m_Light.Specular = D3DXCOLOR(1.0f,1.0f,1.0f,1.0f);
	m_Light.Type = D3DLIGHT_POINT;
	m_Light.Position = D3DXVECTOR3(30, 30, -50);
	m_Light.Range = 100.0f;
	m_Attenuation012 = D3DXVECTOR3(0.0f,0.05f,0.0f);

	//create the material structure
	m_MaterialNorm.Ambient = D3DXCOLOR(0.2f,0.2f,0.2f,1.0f);
	m_MaterialNorm.Diffuse = D3DXCOLOR(1.0f,1.0f,1.0f,1.0f);
	m_MaterialNorm.Specular = D3DXCOLOR(0.0f,0.0f,0.0f,0.0f);
	m_MaterialNorm.Power = 0.0f;
}

void PaperLuigiFramework::createViewMat()
{
	D3DXVec3Normalize(&m_LookAt, &m_LookAt);

	D3DXVec3Cross(&m_UpVec, &m_LookAt, &m_RightVec);
	D3DXVec3Normalize(&m_UpVec, &m_UpVec);

	D3DXVec3Cross(&m_RightVec, &m_UpVec, &m_LookAt);
	D3DXVec3Normalize(&m_RightVec, &m_RightVec);

	float x = -D3DXVec3Dot(&m_EyePos, &m_RightVec);
	float y = -D3DXVec3Dot(&m_EyePos, &m_UpVec);
	float z = -D3DXVec3Dot(&m_EyePos, &m_LookAt);

	m_ViewMat(0,0) = m_RightVec.x;
	m_ViewMat(1,0) = m_RightVec.y;
	m_ViewMat(2,0) = m_RightVec.z;
	m_ViewMat(3,0) = x;

	m_ViewMat(0,1) = m_UpVec.x;
	m_ViewMat(1,1) = m_UpVec.y;
	m_ViewMat(2,1) = m_UpVec.z;
	m_ViewMat(3,1) = y;

	m_ViewMat(0,2) = m_LookAt.x;
	m_ViewMat(1,2) = m_LookAt.y;
	m_ViewMat(2,2) = m_LookAt.z;
	m_ViewMat(3,2) = z;

	m_ViewMat(0,3) = 0.0f;
	m_ViewMat(1,3) = 0.0f;
	m_ViewMat(2,3) = 0.0f;
	m_ViewMat(3,3) = 1.0f;
}

void PaperLuigiFramework::createProjMat()
{
	//Create the Projection matrix and set it for the projection State
	D3DXMatrixPerspectiveFovLH(&m_ProjMat,							//Output Matrix
		D3DXToRadian(65),					//Radian degree measure
		(float)m_nWidth/(float)m_nHeight,	//Aspect Ratio
		0.0f,								//Near plane
		1000.0f);	//Far Plane
}

#pragma endregion

#pragma region Visualization Methods

void PaperLuigiFramework::Update()
{
	timer.update();
	dt = timer.getDT();
	//Making these values static so that they stay throughout function calls
	//Idea gotten from book
	static short numFrames = 0;
	static float timeElapsed = 0.0f;

	//Increment both counts
	numFrames += 1;
	timeElapsed += dt;


	//computing for one second, if one second has passed then
	//set number of frames per seconds which is the numFrames
	//as well as milliseconds per frame
	if(timeElapsed >= 1.0f)
	{
		m_Fps = numFrames;
		m_MillSpf = 1000.0f / m_Fps;

		//reset counters to find values of next second
		numFrames = 0;
		timeElapsed = 0;
		if(state == 4)
		{
		}
	}
	input.detect_input();

	testInput(dt);

	createViewMat();

	if(state == 4)
	{
	}
}

void PaperLuigiFramework::testInput(float dt)
{
	D3DXVECTOR3 dir(0.0f,0.0f,0.0f);



	if(luigi->isBottomCollision())
	{
		if(input.keyDown(DIK_SPACE))
			{
				if(!keyDown[DIK_SPACE])
				{
					luigi->jump();
					keyDown[DIK_SPACE] = true;
				}
			}
			else
			{
				if(keyDown[DIK_SPACE])
				{
					keyDown[DIK_SPACE] = false;
				}
			}
	}

		if(input.keyDown(DIK_LEFT))
		{
			luigi->MovementX(-100, dt);
		}

		if(input.keyDown(DIK_RIGHT))
		{
			luigi->MovementX(100, dt);
		}

		if(input.keyDown(DIK_UP))
		{
			luigi->MovementZ(1, dt);
		}

		if(input.keyDown(DIK_DOWN))
		{
			luigi->MovementZ(-1, dt);
		}

		if(input.keyDown(DIK_A))
		{
			dir -= m_RightVec;
		}

		if(input.keyDown(DIK_D))
		{
			dir += m_RightVec;
		}

		if(input.keyDown(DIK_W))
		{
			dir.x += m_LookAt.x;
		}

		if(input.keyDown(DIK_S))
		{
			dir.x -= m_LookAt.x;
		}

		luigi->gravityEffect(dt);

		D3DXVec3Normalize(&dir,&dir);
		m_EyePos.x += dir.x*m_CameraSpeed*dt;
		m_EyePos.y = 2.5f;
		m_EyePos.z = -30.0f;

		if(m_EyePos.x > 30.0f)
		{
			m_EyePos.x = 30.0f;
		}
		else if(m_EyePos.x < -30.0f)
		{
			m_EyePos.x = -30.0f;
		}


		//m_CameraPitch = mouseState->lY / 150.0f;
		//m_CameraYAngle = mouseState->lX / 150.0f;

		D3DXMATRIX rotation;
		D3DXMatrixRotationAxis(&rotation, &m_RightVec, m_CameraPitch);
		D3DXVec3TransformCoord(&m_LookAt, &m_LookAt, &rotation);
		D3DXVec3TransformCoord(&m_UpVec, &m_UpVec, &rotation);

		D3DXMatrixRotationY(&rotation, m_CameraYAngle);
		D3DXVec3TransformCoord(&m_RightVec, &m_RightVec, &rotation);
		D3DXVec3TransformCoord(&m_UpVec, &m_UpVec, &rotation);
		D3DXVec3TransformCoord(&m_LookAt, &m_LookAt, &rotation);

		createViewMat();
}

void PaperLuigiFramework::Render()
{
	//If our D3D Device was not created, return
	if(!m_pD3DDevice)
		return;

	//clear all of the matrices to identities
	D3DXMatrixIdentity(&rotMat);
	D3DXMatrixIdentity(&scaMat);
	D3DXMatrixIdentity(&transMat);
	D3DXMatrixIdentity(&worldMat);

	// Clear the back-buffer
	m_pD3DDevice->Clear(0,			//clears the amount of buffers
		0,							//Clears a rect value
		D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,			//clears the entire buffer
		D3DXCOLOR(0.0f,0.4f,0.8f,1.0f),	//sets the color of the screen
		1.0f,						//z-buffer value
		0);							//stencil

	//created statically so that memory isn't allocated each time
	//gotten from the book
	static WCHAR buffer[256];
	static WCHAR information[256];

	//Create the rect that will be used for the text
	RECT formatRect = {5,5,0,0};
	RECT name = {500,5,0,0};

	//Begin the rendering process
	m_pD3DDevice->BeginScene();

	////////////////////////////////
	// Sprites
	/////////////////////////////////
	m_Sprite->Begin(D3DXSPRITE_ALPHABLEND);

	D3DXMatrixScaling(&scaMat, .25, .25, 0);
	D3DXMatrixTranslation(&transMat, luigi->getPosition().x, luigi->getPosition().y, 0);
	D3DXMatrixMultiply(&worldMat, &scaMat, &transMat);
	m_Sprite->SetTransform(&worldMat);

	m_Sprite->Draw(luigi->m_TexChar, 0, ((luigi->m_IIChar.Width/2)*.25, (luigi->m_IIChar.Height/2)*.25, 0), 0, D3DCOLOR_XRGB(225, 225, 225));

	m_Sprite->End();

	state = menu.displayMenu(state, currState);

	//Draw text and whatever onto the scene Using
	//sprintf_s for the timer to show frames per second.
	int j;
	int k;

	j = swprintf_s(buffer,
		256,
		L"Frames Per Second = %.2f\n",
		m_Fps);

	j += swprintf_s(buffer +j,
		256 - j,
		L"Milliseconds per frame = %.4f\n",
		m_MillSpf);


	m_Font->DrawTextW(0,						//Sprite Texture
		buffer,			//Text
		-1,							//Count
		&formatRect,				//Rect
		DT_NOCLIP,					//Format
		D3DCOLOR_XRGB(255,255,0)); //Color

	m_Font->DrawTextW(0,						//Sprite Texture
		information,			//Text
		-1,							//Count
		&name,						//Rect
		DT_NOCLIP,					//Format
		D3DCOLOR_XRGB(255,255,0)); //Color

	
	//////////////////////
	// Meshes
	/////////////////////
	D3DXMATRIX matrix;
	D3DXMatrixTranslation(&matrix,  0.0f, 0.0f,  0.0f);
	m_pD3DDevice->SetTransform(D3DTS_WORLD, &matrix);

	// draw the object using the previously created world matrix.
	m_BoxMesh->DrawSubset(0); // box has only 1 subset (AttribId = 0)

	//End the rendering process in general
	m_pD3DDevice->EndScene();

	//Present the back-buffer to the screen
	m_pD3DDevice->Present(0,0,0,0);		//if there is a specific rectangle we want to present add values
}

#pragma endregion

#pragma region Sizing Window Methods

void PaperLuigiFramework::screenResize()
{
	RECT clientRect;
	GetClientRect(m_D3Dpp.hDeviceWindow,&clientRect);
	m_D3Dpp.BackBufferWidth = clientRect.right;
	m_D3Dpp.BackBufferHeight = clientRect.bottom;//need to add this to the directx framework so it can stay private
	onLostDevice();//release the objects required to be released
	m_pD3DDevice->Reset(&m_D3Dpp);
	onResetDevice();//initialize the objects again
}

void PaperLuigiFramework::switchingFullScreen(bool enable)
{
	//if true then switch to fullscreen else to windowed
	if(enable)
	{
		//check to see if we are in fullscreen mode
		if(!m_D3Dpp.Windowed)
			return;

		//Get the screen resolution
		int width = GetSystemMetrics(SM_CXSCREEN);
		int height = GetSystemMetrics(SM_CYSCREEN);

		m_D3Dpp.BackBufferFormat = D3DFMT_X8R8G8B8;		//Back-buffer pixel format
		m_D3Dpp.BackBufferHeight = height;			//Back-Buffer Height
		m_D3Dpp.BackBufferWidth = width;			//Back-Buffer width
		m_D3Dpp.Windowed = false;

		//Change window style to support full screen
		SetWindowLongPtr(m_D3Dpp.hDeviceWindow,GWL_STYLE,WS_POPUP);

		//to get the change to take effect must call setWindowPos
		SetWindowPos(m_D3Dpp.hDeviceWindow, HWND_TOP,0,0,width,height,SWP_NOZORDER | SWP_SHOWWINDOW);
	}

	else
	{
		if(m_D3Dpp.Windowed)
			return;


		RECT R = {0,0,800,600};
		AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
		m_D3Dpp.BackBufferFormat = D3DFMT_X8R8G8B8;		//Back-buffer pixel format
		m_D3Dpp.BackBufferHeight = m_nHeight;			//Back-Buffer Height
		m_D3Dpp.BackBufferWidth = m_nWidth;			//Back-Buffer width
		m_D3Dpp.Windowed = true;					//is windowed

		//Change window style to support windowed
		SetWindowLongPtr(m_D3Dpp.hDeviceWindow,GWL_STYLE,WS_OVERLAPPEDWINDOW);

		//to get the change to take effect must call setWindowPos
		SetWindowPos(m_D3Dpp.hDeviceWindow, HWND_TOP,100,100,R.right,R.bottom,SWP_NOZORDER | SWP_SHOWWINDOW);

		onLostDevice();//release the objects required to be released
		m_pD3DDevice->Reset(&m_D3Dpp);
		onResetDevice();//initialize the objects again
	}
}

#pragma endregion

#pragma region lostDevice Methods

bool PaperLuigiFramework::lostDevice()
{
	HRESULT	hr = m_pD3DDevice->TestCooperativeLevel();

	//If the device is lost and cannot be reset then wait
	if(hr == D3DERR_DEVICELOST)
	{
		Sleep(20);
		return true;
	}

	//if there is an error exit
	else if(hr == D3DERR_DRIVERINTERNALERROR)
	{
		MessageBox(0,L"Internal Driver Error\t\tExiting",0,0);
		PostQuitMessage(0);
		return true;
	}

	//The device is lost but can be reset
	else if(hr == D3DERR_DEVICENOTRESET)
	{
		onLostDevice();//release the objects required to be released
		m_pD3DDevice->Reset(&m_D3Dpp);
		onResetDevice();//initialize the objects again
		//Device is now found
		return false;
	}

	//the device isn't lost at all
	else 
		return false;
}

void PaperLuigiFramework::onLostDevice()
{
	//to be added, things that need to be released when the object is lost and needs to be reset
	m_Sprite->OnLostDevice();
	m_Font->OnLostDevice();
	onLostEffect();
	menu.onLostDevice();
}

void PaperLuigiFramework::onLostEffect()
{
	//call on lost device for shaders
}

void PaperLuigiFramework::onResetDevice()
{
	//to be added, things that need to be initialized again when the object is reset
	m_Sprite->OnResetDevice();
	m_Font->OnResetDevice();
	onResetEffect();
	menu.onResetDevice();
}

void PaperLuigiFramework::onResetEffect()
{
	//call on reset device for shaders
}

#pragma endregion

#pragma region Destruction Methods

void PaperLuigiFramework::shutdownMeshes()
{
	//if there are meshes release here
	SAFE_RELEASE(m_BoxMesh);
}

void PaperLuigiFramework::releaseTextures()
{
	//if there are textures release here
}

void PaperLuigiFramework::Shutdown()
{
	shutdownMeshes();
	input.cleanDInput();
	menu.destroyMenu();
	SAFE_RELEASE(m_Font);
	releaseTextures();
	SAFE_RELEASE(m_Sprite);
	SAFE_RELEASE(m_pD3DDevice);
	SAFE_RELEASE(m_pD3DObject);
	delete luigi;
	delete enemy;
}

#pragma endregion
